package chpn.id;

import chpn.id.util.RedisUtil;
import chpn.id.util.TimerUtil;

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 基于Redis的机器ID生成器
 * 使用Redis确保分布式环境下机器ID的唯一性
 * 支持定时续期当前使用的机器ID
 */
public class RedisWorkerIdGenerator implements WorkerIdGenerator {
    
    // Redis键前缀
    private static final String KEY_PREFIX = "snowflake:worker_id:";
    
    // 最后加入的workerId键
    private static final String LAST_WORKER_ID_KEY = "snowflake:last_worker_id";
    
    // 缓存过期时间（2分钟）
    private static final int EXPIRE_SECONDS = 120;
    
    // 最大重试次数
    private static final int MAX_RETRY = 16;
    
    // 当前机器ID，使用AtomicLong确保线程安全
    private static final AtomicLong currentId = new AtomicLong(new Random().nextInt((int) 0xF));
    
    // 用于同步获取ID的锁
    private static final ReentrantLock lock = new ReentrantLock();
    
    // 定时任务ID
    private static final String RENEW_TASK_ID = "machine-id-renewer";
    
    // 是否已初始化
    private static boolean initialized = false;
    
    // 初始化方法，启动定时续期任务
    private synchronized static void initialize() {
        if (!initialized) {
            // 每分钟执行一次续期操作，延迟1分钟后开始执行
            TimerUtil.scheduleAtFixedRate(
                RENEW_TASK_ID,
                RedisWorkerIdGenerator::renewCurrentId,
                1,
                1,
                TimeUnit.MINUTES
            );
            initialized = true;
        }
    }
    
    /**
     * 续期当前使用的机器ID
     */
    private static void renewCurrentId() {
        long id = currentId.get();
        String key = KEY_PREFIX + id;
        try {
            // 续期当前ID，将过期时间重置为EXPIRE_SECONDS
            boolean success = RedisUtil.setIfNotExists(key, "1", EXPIRE_SECONDS);
            if (!success) {
                // 如果续期失败（可能是ID已经被其他实例占用），尝试获取新的ID
                System.err.println("警告: 机器ID " + id + " 续期失败，可能已被其他实例占用");
            }
        } catch (Exception e) {
            System.err.println("续期机器ID时发生异常: " + e.getMessage());
        }
    }
    
    /**
     * 构造函数，确保初始化定时任务
     */
    public RedisWorkerIdGenerator() {
        initialize();
    }
    
    @Override
    public long getNewWorkerId() {
        // 使用锁确保线程安全
        lock.lock();
        try {
            // 从Redis获取最后加入的workerId，如果没有则从0开始
            long lastWorkerId = RedisUtil.getLong(LAST_WORKER_ID_KEY, -1);
            long tempId = (lastWorkerId + 1) & 0xF;
            
            for (int i = 0; i < MAX_RETRY; i++) {
                // 尝试在Redis中设置这个ID
                String key = KEY_PREFIX + tempId;
                boolean success = RedisUtil.setIfNotExists(key, "1", EXPIRE_SECONDS);
                
                if (success) {
                    // 设置成功，更新最后加入的workerId到Redis
                    RedisUtil.set(LAST_WORKER_ID_KEY, String.valueOf(tempId), EXPIRE_SECONDS);
                    // 更新当前ID并返回
                    currentId.set(tempId);
                    return tempId;
                }
                
                // 如果设置失败（ID已存在），尝试下一个ID
                tempId = (tempId + 1) & 0xF;
            }
            throw new RuntimeException("无法获取可用机器ID");
        } finally {
            lock.unlock();
        }
    }
    
    /**
     * 关闭资源
     * 在应用程序关闭时调用，释放资源
     */
    public static void closePool() {
        // 取消定时续期任务
        TimerUtil.cancelTask(RENEW_TASK_ID, true);
        
        // 关闭定时任务执行器
        TimerUtil.shutdown();
        
        // 关闭Redis连接池
        RedisUtil.closePool();
    }

    /**
     * 清理所有机器ID
     * 删除Redis中所有以KEY_PREFIX开头的键和最后加入的workerId
     * 用于测试前清理环境
     *
     * @return 删除的键数量
     */
    public static long clearAllWorkerIds() {
        // 删除Redis中所有的机器ID
        long count = RedisUtil.deleteByPrefix(KEY_PREFIX);
        
        // 删除最后加入的workerId
        if (RedisUtil.delete(LAST_WORKER_ID_KEY)) {
            count++;
        }
        
        return count;
    }

}